package simpledb.storage;

import simpledb.common.Database;
import simpledb.common.DbException;
import simpledb.common.Debug;
import simpledb.common.Permissions;
import simpledb.transaction.TransactionAbortedException;
import simpledb.transaction.TransactionId;

import java.io.*;
import java.util.*;

/**
 * HeapFile is an implementation of a DbFile that stores a collection of tuples
 * in no particular order. Tuples are stored on pages, each of which is a fixed
 * size, and the file is simply a collection of those pages. HeapFile works
 * closely with HeapPage. The format of HeapPages is described in the HeapPage
 * constructor.
 *
 * @see HeapPage#HeapPage
 * @author Sam Madden
 */
public class HeapFile implements DbFile {

    private final File f;
    private final TupleDesc td;

    /**
     * Constructs a heap file backed by the specified file.
     *
     * @param f
     *            the file that stores the on-disk backing store for this heap
     *            file.
     */
    public HeapFile(File f, TupleDesc td) {
        // some code goes here
        this.f = f;
        this.td = td;
    }

    /**
     * Returns the File backing this HeapFile on disk.
     *
     * @return the File backing this HeapFile on disk.
     */
    public File getFile() {
        // some code goes here
        return f;
    }

    /**
     * Returns an ID uniquely identifying this HeapFile. Implementation note:
     * you will need to generate this tableid somewhere to ensure that each
     * HeapFile has a "unique id," and that you always return the same value for
     * a particular HeapFile. We suggest hashing the absolute file name of the
     * file underlying the heapfile, i.e. f.getAbsoluteFile().hashCode().
     * 使用磁盘文件的绝对路径哈希值作为唯一 ID，确保同一文件的 ID 始终相同
     *
     * @return an ID uniquely identifying this HeapFile.
     */
    public int getId() {
        // some code goes here
        // throw new UnsupportedOperationException("implement this");
        return f.getAbsolutePath().hashCode();
    }

    /**
     * Returns the TupleDesc of the table stored in this DbFile.
     *
     * @return TupleDesc of this DbFile.
     */
    public TupleDesc getTupleDesc() {
        // some code goes here
        // throw new UnsupportedOperationException("implement this");
        return td;
    }

    // see DbFile.java for javadocs
    public Page readPage(PageId pid) {
        // 1. 验证pid类型是否为HeapPageId
        if (!(pid instanceof HeapPageId hpid)) {
            throw new IllegalArgumentException("PageId must be an instance of HeapPageId");
        }

        // 2. 验证页面所属表ID是否匹配当前HeapFile
        int tableId = hpid.getTableId();
        if (tableId != this.getId()) {
            throw new IllegalArgumentException(
                    String.format("Page belongs to table %d, but current HeapFile is table %d",
                            tableId, this.getId())
            );
        }

        int pgNo = hpid.getPageNumber();
        int pageSize = BufferPool.getPageSize();
        RandomAccessFile raf = null;

        try {
            raf = new RandomAccessFile(f, "r");
            long fileLength = raf.length(); // 使用raf获取文件长度（更准确，避免文件被并发修改）

            // 3. 验证页面编号是否合法（页面范围是否在文件内）
            long pageOffset = (long) pgNo * pageSize;
            if (pageOffset + pageSize > fileLength) {
                throw new IllegalArgumentException(
                        String.format("Page %d of table %d is out of bounds. " +
                                        "File length: %d bytes, Page offset: %d, Page size: %d",
                                pgNo, tableId, fileLength, pageOffset, pageSize)
                );
            }

            // 4. 读取页面数据
            byte[] pageData = new byte[pageSize];
            raf.seek(pageOffset); // 定位到页面起始位置
            int bytesRead = raf.read(pageData, 0, pageSize);

            // 5. 验证读取的字节数是否完整
            if (bytesRead != pageSize) {
                throw new IOException(
                        String.format("Failed to read full page. Expected %d bytes, got %d bytes. " +
                                        "Table: %d, Page: %d, Offset: %d",
                                pageSize, bytesRead, tableId, pgNo, pageOffset)
                );
            }

            // 6. 构造并返回HeapPage
            return new HeapPage(hpid, pageData);

        } catch (IOException e) {
            // 封装IO异常，添加上下文信息
            throw new RuntimeException(
                    String.format("Error reading page %d of table %d: %s",
                            pgNo, tableId, e.getMessage()),
                    e // 保留原始异常堆栈，便于调试
            );
        } finally {
            // 7. 确保文件流关闭，处理关闭时的异常
            if (raf != null) {
                try {
                    raf.close();
                } catch (IOException e) {
                    // 关闭异常不影响主逻辑，但记录警告
                    System.err.println("Warning: Failed to close RandomAccessFile: " + e.getMessage());
                }
            }
        }
    }

    // see DbFile.java for javadocs
    public void writePage(Page page) throws IOException {
        // System.out.println("写入页面");
        // some code goes here
        // not necessary for lab1
        int pageNumber = page.getId().getPageNumber();
        RandomAccessFile randomAccessFile = null;
        try {
            // 1. 初始化随机访问文件
            randomAccessFile = new RandomAccessFile(f, "rw");

            // 2. 计算写入位置（确保pageNumber非负，避免seek到负数位置）
            if (pageNumber < 0) {
                throw new IllegalArgumentException("无效的页号：" + pageNumber);
            }
            long offset = (long) pageNumber * BufferPool.getPageSize();

            // 3. 定位到目标位置并写入数据
            randomAccessFile.seek(offset);
            byte[] pageData = page.getPageData();
            randomAccessFile.write(pageData);

        } catch (IOException e) {
            // 4. 捕获IO异常并处理（如日志记录或向上抛出）
            throw new RuntimeException("写入页面数据失败，页号：" + pageNumber, e);
        } finally {
            // 4. 确保文件流无论是否异常都被关闭，避免资源泄露
            if (randomAccessFile != null) {
                try {
                    randomAccessFile.close();
                } catch (IOException e) {
                    // 关闭时的异常可记录但无需中断流程
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * Returns the number of pages in this HeapFile.
     */
    public int numPages() {
        // some code goes here
        long fileLength = f.length();
        int pageSize = BufferPool.getPageSize();
        if (fileLength <= 0) {
            return 0; // 空文件返回0页（根据业务需求调整）
        }
        // 整数运算：商 + 余数是否为0的判断
        return (int) ((fileLength + pageSize - 1) / pageSize);
    }

    // see DbFile.java for javadocs
    public List<Page> insertTuple(TransactionId tid, Tuple t)
            throws DbException, IOException, TransactionAbortedException {
        // 打印插入操作开始日志，包含事务ID和元组信息
        System.out.printf("[TID" + tid.getId() + "]" + " 事务 %s 开始插入元组: %s%n", tid, t);

        List<Page> modifiedPages = new ArrayList<>();
        BufferPool bufferPool = Database.getBufferPool(); // 提前获取缓冲池引用，避免重复调用
        int totalPages = numPages(); // 缓存页面总数，减少重复计算

        // 打印当前文件的页面总数
        System.out.printf("[TID" + tid.getId() + "]" + " 当前文件总页数: %d，开始查找有空槽的页面%n", totalPages);

        // 1. 尝试在已有页面中插入
        for (int i = 0; i < totalPages; i++) {
            PageId pageId = new HeapPageId(getId(), i);
            System.out.printf("[TID" + tid.getId() + "]" + " 尝试访问页面 %d（PageId: %s）%n", i, pageId.getPageNumber());

            // 从缓冲池获取页面（加读写锁）
            HeapPage page = (HeapPage) bufferPool.getPage(tid, pageId, Permissions.READ_WRITE);
            System.out.printf("[TID" + tid.getId() + "]" + " 成功获取页面 %d，当前空槽数量: %d%n",
                    i, page.getNumEmptySlots());

            // 找到有空槽的页面，插入后直接返回
            if (page.getNumEmptySlots() > 0) {
                page.insertTuple(t);
                modifiedPages.add(page);
                System.out.printf("[TID" + tid.getId() + "]" + " 成功插入元组%s到页面 %d，剩余空槽: %d%n",
                        t, i, page.getNumEmptySlots());
                return modifiedPages;
            } else {
                // 页面无空槽，释放页面锁
                bufferPool.unsafeReleasePage(tid, pageId);
                System.out.printf("[TID" + tid.getId() + "]" + "页面 %d 无空槽，已释放锁%n", i);
            }
        }

        // 2. 所有页面已满，创建新页面
        System.out.printf("[TID" + tid.getId() + "]" + "所有 %d 个页面均无空槽，开始创建新页面%n", totalPages);

        // 使用try-with-resources自动管理流，替代手动close()
        try (BufferedOutputStream outputStream = new BufferedOutputStream(
                new FileOutputStream(this.f, true))) {
            byte[] emptyPageData = HeapPage.createEmptyPageData();
            outputStream.write(emptyPageData);
            System.out.printf("[TID" + tid.getId() + "]" + "新页面数据写入完成，数据长度: %d字节（页大小）%n",
                    emptyPageData.length);
        }

        // 3. 在新页面中插入元组
        PageId newPageId = new HeapPageId(getId(), totalPages);
        System.out.printf("[TID" + tid.getId() + "]" + "新页面ID: %s，尝试从缓冲池获取页面%n", newPageId);

        HeapPage newPage = (HeapPage) bufferPool.getPage(tid, newPageId, Permissions.READ_WRITE);
        newPage.insertTuple(t);
        modifiedPages.add(newPage);

        System.out.printf("[TID" + tid.getId() + "]" + "元组成功插入新页面 %d，剩余空槽: %d%n",
                totalPages, newPage.getNumEmptySlots());

        return modifiedPages;
    }

    // see DbFile.java for javadocs
    public ArrayList<Page> deleteTuple(TransactionId tid, Tuple t) throws DbException,
            TransactionAbortedException {
        // some code goes here
        ArrayList<Page> modifiedPages = new ArrayList<>();
        RecordId recordId = t.getRecordId();
        PageId pageId = recordId.getPageId();
        if (pageId.getTableId()!=getId()) {
            throw new DbException("delete wrong file");
        }
        HeapPage page = (HeapPage) Database.getBufferPool().getPage(tid, pageId, Permissions.READ_WRITE);
        System.out.println("[transactionComplete] [TID" + tid + " 从页面 " + t.getRecordId().getPageId().getPageNumber() + " 删除元组：" + t);
        page.deleteTuple(t);
        modifiedPages.add(page);
        return modifiedPages;
        // not necessary for lab1
    }

    private class HeapFileIterator implements DbFileIterator {

        private final TransactionId tid;
        private int curPageNum;
        private final int totalPages;
        private Iterator<Tuple> iterator;

        public HeapFileIterator(TransactionId tid) {
            this.tid = tid;
            this.curPageNum = 0;
            this.totalPages = HeapFile.this.numPages();
            this.iterator = null;
        }

        /**
         * Opens the iterator
         *
         * @throws DbException when there are problems opening/accessing the database.
         */
        @Override
        public void open() throws DbException, TransactionAbortedException {
            curPageNum = 0;
            if (totalPages>0) {
                iterator = getPageTupleIterator(curPageNum);
            }
        }

        /*
         * 获取指定页面的迭代器
         */
        private Iterator<Tuple> getPageTupleIterator(int pageNum) throws DbException, TransactionAbortedException {

            // 构造页面ID（使用外部类HeapFile的ID和当前页码）
            HeapPageId pid = new HeapPageId(HeapFile.this.getId(), pageNum);
            // 从缓冲池获取页面（加读锁）
            HeapPage page = (HeapPage) Database.getBufferPool().getPage(tid, pid, Permissions.READ_ONLY);
            // 返回页面内的有效元组迭代器（自动跳过空槽位）
            return page.iterator();
        }

        /**
         * @return true if there are more tuples available, false if no more tuples or iterator isn't open.
         */
        @Override
        public boolean hasNext() throws DbException, TransactionAbortedException {
            // 尚未open或者已经close
            if (iterator==null) {
                return false;
            }
            if (iterator.hasNext()) {
                return true;
            }
            // 当前页面已经遍历完了，需要进入下一个页面
            while (true) {
                curPageNum++;
                // 页面已经遍历完了
                if (curPageNum >= totalPages) {
                    return false;
                }
                // 加载新页的迭代器
                iterator = getPageTupleIterator(curPageNum);
                // 如果新页有下一个，就返回true，否则，说明加载的新页是空的需要再加载一页
                if (iterator.hasNext()) {
                    return true;
                }
            }
        }

        /**
         * Gets the next tuple from the operator (typically implementing by reading
         * from a child operator or an access method).
         *
         * @return The next tuple in the iterator.
         * @throws NoSuchElementException if there are no more tuples
         */
        @Override
        public Tuple next() throws DbException, TransactionAbortedException, NoSuchElementException {
            // 没有下一个了
            if (!hasNext()) {
                throw new NoSuchElementException("No more tuples in HeapFile");
            }
            // 在hasnext中iterator已经更新了
            return iterator.next();
        }

        /**
         * Resets the iterator to the start.
         *
         * @throws DbException When rewind is unsupported.
         */
        @Override
        public void rewind() throws DbException, TransactionAbortedException {
            close();
            open();
        }

        /**
         * Closes the iterator.
         */
        @Override
        public void close() {
            curPageNum = 0;
            iterator = null;
        }
    }

    // see DbFile.java for javadocs
    public DbFileIterator iterator(TransactionId tid) {
        // some code goes here
        return new HeapFileIterator(tid);
    }

}